<?php

/**
 * SigmaCMS - Content Management System
 *
 * Copyright (C) 2008 Pavol Biely
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package    SigmaCMS
 * @author     Pavol Biely <pavolbiely@gmail.com>
 * @copyright  2008 Pavol Biely
 * @license    http://www.gnu.org/licenses/gpl.txt   GNU General Public License
 * @link       http://pabi3.com/
 * @since      0.1.0
 */

final class Driver
{
	//////////////////////////////// plugins /////////////////////////////////////

	/**
	 * Call a plugin method which name is starting with "parse_"
	 *
	 * @param string $name
	 * @param string $method
	 * @return void
	 */
	public function parse_plugin($name, $method)
	{
		// is the plugin registered?
		if (Plugin::IsRegistered($name)) {
			// plugin instance
			$plugin = Plugin::GetPlugin($name);

			// build a valid method name
			$method = 'parse_'.$method;

			// additional parameters
			$params = func_get_args();

			// skip first two parameters
			array_splice($params,0,2);

			// is the method available in the plugin?
			if (method_exists($plugin,$method)) {
				call_user_func_array(array($plugin,$method),$params);
			} else {
				$trace = $name.'::'.$method.'('.implode(',',$params).')';
				Actionlog::Add(sprintf(_LOG_DRIVER_PLUGIN_UNKNOWN,$trace));
			}
		} else {
			Actionlog::Add(sprintf(_LOG_DRIVER_PLUGIN_UNREGISTERED,$name));
		}
	}

	///////////////////////////////// templates //////////////////////////////////

	/**
	 * Array of Objects Template; it is set after using the Parser::setTemplate() method
	 *
	 * @var array
	 */
	public $template = array();

	/**
	 * Print a keyword from template
	 *
	 * @param string $keyword
	 * @return void
	 */
	public function parse_template($keyword)
	{
		// template layers
		$index = count((array) $this->template)-1;
		if ($index < 0) return;

		// does the template instance exist?
		if (!is_object($this->template[$index])) return;

		// print template keyword if it is available
		if (array_key_exists($keyword, $this->template[$index]->params)) {
			echo $this->template[$index]->getParam($keyword);
		}
	}

	///////////////////////////////// events /////////////////////////////////////

	/**
	 * Define a place where events will be called
	 *
	 * @return void
	 */
	public function parse_event()
	{
		// load every registered plugin into an array
		$plugins = Plugin::GetPlugins();

		// event parameters
		$params = func_get_args();

		// event name
		$eventName = $params[0];

		// event name must contain only alphanumeric characters
		if (!ctype_alnum($eventName)) {
			return;
		}

		// skip the first parameter
		array_shift($params);

		foreach ($plugins as $plugin) {
			// build a valid event name
			$event = 'event_'.$eventName;
			// is the event available in the plugin?
			if (method_exists($plugin, $event)) {
				call_user_func_array(array($plugin, $event), $params);
			}
		}
	}

	////////////////////////// superglobal variables ////////////////////////////

    /**
     * Print value of the superglobal array $_GET
     *
     * @param string $key
     * @param boolean $entities
     * @return mixed
     */
	public function parse_get($key, $entities = true)
	{
		if ($entities) {
			$entities = _hsc($entities);
		}
		echo _get($key);
	}

    /**
     * Print value of the superglobal array $_POST
     *
     * @param string $key
     * @param boolean $entities
     * @return mixed
     */
	public function parse_post($key, $entities = true)
	{
		if ($entities) {
			$entities = _hsc($entities);
		}
		echo _post($key);
	}

    /**
     * Print value of the superglobal array $_SERVER
     *
     * @param string $key
     * @param boolean $entities
     * @return mixed
     */
	public function parse_server($key, $entities = true)
	{
		if ($entities) {
			$entities = _hsc($entities);
		}
		echo _server($key);
	}

    /**
     * Print value of the superglobal array $_ENV
     *
     * @param string $key
     * @param boolean $entities
     * @return mixed
     */
	public function parse_env($key, $entities = true)
	{
		if ($entities) {
			$entities = _hsc($entities);
		}
		echo _env($key);
	}

	/////////////////////////////// conditions /////////////////////////////////

	/**
	 * Condition level
	 *
	 * @var array
	 */
	private $_condition = array();

	/**
	 * IF conditions
	 *
	 * @var array
	 */
	private $_conIf = array();

	/**
	 * ELSEIF conditions
	 *
	 * @var array
	 */
	private $_conElseIf = array();

	/**
	 * Evaluate condition
	 *
	 * @param string $key
	 * @param array $params
	 * @return boolean
	 */
	private function getCondition($key, array $params = array())
	{
		// default return value
		$condition = false;

		// available build-in keywords
		switch ($key) {
			// check if the superglobal is set
			case 'isset':
				if (strlen(trim($params[1])) > 0) {
					if (_request($params[0]) == $params[1]) {
						$condition = true;
					}
				} else if (isset($_REQUEST[$params[0]])) {
					$condition = true;
				}
				break;

			// check if the concrete skin is used
			case 'skin':
				$skin = strtolower(trim($params[0]));
				if ($skin == Main::$skin) {
					$condition = true;
				}
				break;

			// check if a member is logged in
			case 'isloggedin':
				// member
				$username = $params[0];

				// if the username it not set, check if any member is logged in
				if (!$username) {
					$condition = Member::isLoggedIn();
					break;
				}

				// check if the specified member is logged in
				$mem = db::prefix('members');
				$ses = db::prefix('members_sessions');
				$loggedIn = db::fQuery("SELECT $ses.`memberid` FROM $mem LEFT JOIN $ses ON $mem.`id`=$ses.`memberid` WHERE $mem.`username`='".db::escape($username)."'");
				if ($loggedIn) {
					$condition = true;
				}
				break;

			// has the logged in member access to the section?
			case 'hasaccess':
				if (Member::hasAccess($params[0])) {
					$condition = true;
				}
				break;

			// compare client's browser with the specified
			case 'browser':
				if (stristr(_server('HTTP_USER_AGENT'),$params[0])) {
					$condition = true;
				}
				break;

			// compare client's IP address with the specified
			case 'ip':
				if (_ip() == $params[0]) {
					$condition = true;
				}
				break;

			// does the client's browser support the real XHTML MIME type?
			case 'xhtml':
				if (isset($_SERVER['HTTP_ACCEPT']) && stristr(_server('HTTP_ACCEPT'),'application/xhtml+xml')) {
					$condition = true;
				}
				break;

			// is the plugin activated/registered?
			case 'plugin':
				if (Plugin::isRegistered($params[0])) {
					$condition = true;
				}
				break;

			// check the template keyword
			case 'template':
				// template layers
				$index = count((array) $this->template)-1;
				if ($index < 0) $condition = false;

				// template object must be set
				if (is_object($this->template[$index])) {
					// does the template include the specified keyword?
					if (array_key_exists($params[0], $this->template[$index]->params)) {
						// comparing operators
						if (isset($params[1],$params[2])) {
							switch ($params[1]) {
								case '!=':
									if ($this->template[$index]->params[$params[0]] != $params[2]) $condition = true;
									break;
								case '=':
								case '==':
									if ($this->template[$index]->params[$params[0]] == $params[2]) $condition = true;
									break;
								case '<=':
									if ($this->template[$index]->params[$params[0]] <= $params[2]) $condition = true;
									break;
								case '>=':
									if ($this->template[$index]->params[$params[0]] >= $params[2]) $condition = true;
									break;
								case '<':
									if ($this->template[$index]->params[$params[0]] < $params[2]) $condition = true;
									break;
								case '>':
									if ($this->template[$index]->params[$params[0]] > $params[2]) $condition = true;
									break;
								default:
									if ($this->template[$index]->params[$params[0]] != NULL) $condition = true;
									break;
							}
						}

						// template keyword not empty?
						else if ($this->template[$index]->params[$params[0]] != NULL) {
							$condition = true;
						}
					}
				}
				break;

			// comparing operators
			default:
				if (isset($params[0],$params[1])) {
					switch ($params[0]) {
						case '!=':
							if ($key != $params[1]) $condition = true;
							break;
						case '=':
						case '==':
							if ($key == $params[1]) $condition = true;
							break;
						case '<=':
							if ($key <= $params[1]) $condition = true;
							break;
						case '>=':
							if ($key >= $params[1]) $condition = true;
							break;
						case '<':
							if ($key < $params[1]) $condition = true;
							break;
						case '>':
							if ($key > $params[1]) $condition = true;
							break;
					}
				} else if ($key && !preg_match('/^&/',$key)) {
					$condition = true;
				}
				break;
		}

		return $condition;
	}

	/**
	 * IF condition
	 *
	 * @param string $key
	 * @return void
	 */
	public function parse_if($key)
	{
		// negation
		$negate = false;

		// additional parameters
		$params = func_get_args();

		// skip the first parameter
		array_shift($params);
		
		// if the first character of the first parameter is exclamation mark (!), it is negation
		if ($key{0} == '!') {
			$negate = true;
			$key    = substr($key, 1);
		}

		// evaluate condition
		$condition = $this->getCondition($key, $params);

		// negate condition?
		if ($negate) $condition = $condition ? false : true;

		// save condition level to array
		array_push($this->_condition, $condition);

		// save condition result
		$this->_conIf[count($this->_condition)] = $condition;

		// halt/enable parser
		Parser::singleton()->halt(!$condition);

		// start buffering
		ob_start();
	}

	/**
	 * ELSEIF condition
	 *
	 * @param string $key
	 * @return void
	 */
	public function parse_elseif($key)
	{
		$total  = count($this->_condition);
		$result = array_pop($this->_condition);

		// prevent unnecessary evaluation of conditions
		if (isset($this->_conElseIf[$total])) {
			if ($this->_conElseIf[$total] === true) {
				if ($result) {
					ob_end_flush();
				} else {
					ob_end_clean();
				}
				array_push($this->_condition, false);

				// halt parser
				Parser::singleton()->halt(true);

				ob_start();
				return;
			}
		}

		// negation
		$negate = false;

		// additional parameters
		$params = func_get_args();

		// skip the first parameter
		array_shift($params);
		
		// if the first character of the first parameter is exclamation mark (!), it is negation
		if ($key{0} == '!') {
			$negate = true;
			$key    = substr($key,1);
		}

		// evaluate condition
		$condition = $this->getCondition($key, $params);

		// negate condition?
		if ($negate) $condition = $condition ? false : true;

		// evalute condition and end buffering
		if ($condition) {
			if ($result) {
				ob_end_flush();
			} else {
				ob_end_clean();
			}
			array_push($this->_condition, true);
		} else {
			if ($result) {
				ob_end_flush();
			} else {
				ob_end_clean();
			}
			array_push($this->_condition, false);
		}
		ob_start();

		// save information about "successfull" ELSEIF condition
		if ($condition == true) {
			$this->_conElseIf[count($this->_condition)] = $condition;
		}

		// halt parser
		Parser::singleton()->halt(!$condition);
	}

	/**
	 * ELSE condition
	 *
	 * @return void
	 */
	public function parse_else()
	{
		// evaluate condition, if the previous used condition was ELSEIF
		$total = count($this->_condition);
		if (isset($this->_conElseIf[$total])) {
			if ($this->_conElseIf[$total] === true) {
				if (array_pop($this->_condition)) {
					ob_end_flush();
				} else {
					ob_end_clean();
				}
				array_push($this->_condition, false);

				// halt parser
				Parser::singleton()->halt(true);

				ob_start();
				return;
			}
		}

		// evaluate condition and end buffering
		if (array_pop($this->_condition)) {
			$result = false;
			ob_end_flush();
			array_push($this->_condition, $result);
		} else {
			$result = !$this->_conIf[$total];
			ob_end_clean();
			array_push($this->_condition, $result);
		}

		// halt parser
		Parser::singleton()->halt(!$result);

		ob_start();
	}

	/**
	 * ENDIF condition
	 * 
	 * @return void
	 */
	public function parse_endif()
	{
		// current condition level
		$total = count($this->_condition);

		// erase needless conditions
		unset($this->_conIf[$total]);
		unset($this->_conElseIf[$total]);

		// evaluate condition and end buffering
		if (array_pop($this->_condition)) {
			ob_end_flush();
		} else {
			ob_end_clean();
		}

		// enable parser
		Parser::singleton()->halt(false);
	}

	/////////////////////////////////// members ///////////////////////////////////

	/**
	 * Print the member's username
	 *
	 * @return void
	 */
	public function parse_member()
	{
		// member must be logged in
		if (!Member::isLoggedIn()) return;

		// print username
		echo Member::getUsername();
	}

	///////////////////////////////// PHP code ///////////////////////////////////

	/**
	 * PHP analyser
	 *
	 * @var boolean
	 */
	private $_php = false;

	/**
	 * Execute the PHP code
	 *
	 * @return void
	 */
	public function parse_php()
	{
		if ($this->_php || !cfg::PARSE_PHP) return;

		$this->_php = true;
		ob_start();
	}

	/**
	 * Stop executing the PHP code
	 *
	 * @return void
	 */
	public function parse_endphp()
	{
		if (!$this->_php || !cfg::PARSE_PHP) return;

		$buffer = ob_get_contents();
		ob_end_clean();

		eval($buffer);

		$this->_php = false;
	}

	///////////////////////////////// files includes /////////////////////////////////

	/**
	 * Root directory of files
	 *
	 * @var string
	 */
	private $_include_dir;

	/**
	 * Array used to prevent a recursion of included files
	 *
	 * @var array
	 */
	private $_recursive = array();

	/**
	 * Including of external files
	 *
	 * @param string $value
	 * @param boolean $parse
	 * @return void
	 */
	public function parse_include($value, $parse = true)
	{
		// do not allow including remote files
		if (preg_match('/^(\/|(http|https|ftp|ftps)+:\/\/)/i',$value)) return;

		// file cannot include itself
		if (!array_key_exists($value,$this->_recursive)) {
			$this->_recursive[$value] = true;
		} else {
			return;
		}

		// root directory for the used skin
		$this->_include_dir = cfg::DIR_SKINS.cfg::get('skin');

		if (is_file($this->_include_dir.$value)) {
			if (preg_match('/\.php$/',$value)) {
				// can parser execute the PHP code?
				if (!cfg::PARSE_PHP) return;

				// load a file content generated by the PHP script
				ob_start();
				include $this->_include_dir.$value;
				$content = ob_get_contents();
				ob_end_clean();
			} else {
				// load the file content
				$content = @file_get_contents($this->_include_dir.$value);
			}

			// analyse a code by the parser?
			if ($parse && !empty($content)) {
				Parser::singleton()->analyze($content);
			} else {
				echo $content;
			}
		}
	}

	///////////////////// paths, uploaded files, videos, images... ////////////////////

	/**
	 * Print the path to currently used skins
	 *
	 * @param boolean
	 * @return void
	 */
	public function parse_skinpath($absolute = false)
	{
	    $path = cfg::DIR_SKINS.cfg::get('skin');
	    if ($absolute) {
            $path = cfg::URL.$path;
	    }
	    echo $path;
	}

	/**
	 * Name of the currently used skin
	 *
	 * @return void
	 */
	public function parse_skin()
	{
		echo Main::$skin;
	}

	/**
	 * Print XHTML element <img />
	 *
	 * @param string $src
	 * @param string $alt
	 * @param string $title
	 * @param string $class
	 * @param integer $width
	 * @param integer $height
	 * @return void
	 */
	public function parse_image($src, $alt, $title = NULL, $class = NULL, $width = NULL, $height = NULL)
	{
		// the image path and the alternative description is required
		if (!$src || !$alt) {
			return;
		}

		// clear image path
		$src = Media::clearPath($src);

		// if the image path is not absolute, it will be extended by the name of upload directory
		if (!preg_match('/^(http|ftp)s?:///',$src)) {
			$src = cfg::DIR_UPLOAD.'images/'.$src;
		}

		// XHTML element <img />
		$image = "<img src='"._hsc($src)."' alt='"._hsc($alt)."'";

		// is a title defined?
		if ($title) {
			$image .= " title='"._hsc($title)."'";
		}

		// is a class defined?
		if ($class) {
			$image .= " class='"._hsc($class)."'";
		}

		// isa  width defined?
		if ($width) {
			$image .= " width='".intval($width)."'";
		}

		// is a height defined?
		if ($height) {
			$image .= " height='".intval($height)."'";
		}

		// end of XHTML element <img />
		$image .= " />\n";

		// print the image element
		echo $image;
	}

	///////////////////////////////// date & time ///////////////////////////////////

	/**
	 * Print date/time
	 *
	 * @param string $format
	 * @param integer $timestamp
	 * @return void
	 */
	public function parse_date($format,$timestamp = NULL)
	{
		if (!$timestamp) {
			$timestamp = time();
		}
		echo date($format,$timestamp);
	}

	/**
	 * Print GMT date/time
	 *
	 * @param string $format
	 * @param integer $timestamp
	 * @return void
	 */
	public function parse_gmdate($format,$timestamp = NULL)
	{
		if (!$timestamp) {
			$timestamp = time();
		}
		echo gmdate($format,$timestamp);
	}

	////////////////////////////////// other ////////////////////////////////////

	/**
	 * Print the directory for plugins
	 *
	 * @return void
	 */
	public function parse_plugins()
	{
		echo cfg::DIR_PLUGINS;
	}

	/**
	 * Print the directory for media
	 *
	 * @return void
	 */
	public function parse_media()
	{
		echo cfg::DIR_UPLOAD;
	}

    /**
     * Print the version of this SigmaCMS copy
     *
     * @return void
     */
	public function parse_sigmacms()
	{
		echo cfg::VERSION;
	}

    /**
     * Print the configured URL address
     *
     * @return void
     */
	public function parse_url()
	{
		echo cfg::URL;
	}
}
